﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;
using NotAClue;
using NotAClue.Web;
using NotAClue.ComponentModel.DataAnnotations;
using NotAClue.Web.DynamicData;
using System.Text;

namespace DD_Extensions
{
    public partial class CascadeHierarchicalFilter : System.Web.DynamicData.QueryableFilterUserControl
    {
        #region member variables
        private const string NullValueString = "[null]";
        // hold the current data context to access the model
        private object context;
        // hold the list of filters
        private SortedList<int, HierachicalControlList> filters = new SortedList<int, HierachicalControlList>();
        // hold the attribute
        private CascadeHierarchicalAttribute cascadeHierarchicalAttribute;
        #endregion

        private new MetaForeignKeyColumn Column
        {
            get { return (MetaForeignKeyColumn)base.Column; }
        }

        public override Control FilterControl
        {
            get
            {
                if (filters[0] != null && filters[0].ListControl != null)
                    return filters[0].ListControl;
                else
                    return null;
            }
        }

        protected void Page_Init(object sender, EventArgs e)
        {
            // setup filter
            this.SetUpFilter(Column);

            //if (!Column.IsRequired)
            // check we have a cascade hierarchical attribute if not throw error
            cascadeHierarchicalAttribute = Column.GetAttribute<CascadeHierarchicalAttribute>();
            if (cascadeHierarchicalAttribute == null)
                throw new InvalidOperationException("Was expecting a CascadeFilterAttribute.");

            // check we have correct column type if not throw error
            if (!(Column is MetaForeignKeyColumn))
                throw new InvalidOperationException(String.Format("Column {0} must be a foreign key column navigation property", Column.Name));

            // get current context
            context = Column.Table.CreateContext();

            // get hierarchical cascade columns
            var parentColumns = new SortedList<int, String>();
            for (int i = 0; i < cascadeHierarchicalAttribute.Parameters.Length; i++)
                parentColumns.Add(i, cascadeHierarchicalAttribute.Parameters[i]);

            // add extra column to represent this column itself
            parentColumns.Add(cascadeHierarchicalAttribute.Parameters.Length, String.Empty);

            //get current column into a local variable
            MetaForeignKeyColumn currentColumn = Column;

            // setup list of filter definitions
            for (int i = 0; i < parentColumns.Count; i++)
            {
                // get parent column name
                var parentColumnName = parentColumns[i];

                // create drop down list
                var ddl = new DropDownList()
                {
                    ID = String.Format("ListControl{0}", i),
                    Enabled = false,
                    CssClass = "DDDropDown",
                    AutoPostBack = true
                };

                // create filter
                var filter = new HierachicalControlList(ddl) { Column = currentColumn };

                // check for last parent filter
                if (!String.IsNullOrEmpty(parentColumnName))
                {
                    // set parent column from parent table
                    filter.ParentColumn = (MetaForeignKeyColumn)currentColumn.ParentTable.GetColumn(parentColumnName);

                    // set current column to parent column
                    currentColumn = filter.ParentColumn;
                }
                else
                {
                    // this is the last parent and has
                    // no parent itself so set to null
                    filter.ParentColumn = null;
                    currentColumn = null;
                }
                // add filter to list of filters
                filters.Add(i, filter);
            }

            // add dropdown list to page in correct order 2, 1, 0
            // last parent, parent<N>, child
            for (int i = parentColumns.Count - 1; i >= 0; i--)
            {
                // setup dropdown list
                filters[i].ListControl.Items.Clear();
                filters[i].ListControl.Items.Add(new ListItem("------", String.Empty));

                // add parent list controls event handler
                if (i > 0)
                    filters[i].ListControl.SelectedIndexChanged += ListControls_SelectedIndexChanged;
                else
                    filters[i].ListControl.SelectedIndexChanged += ListControl0_SelectedIndexChanged;

                // add control to place holder
                this.Controls.Add(filters[i].ListControl);
            }

            if (DefaultValue != null)
            {
                // get parent table
                var parentTable = Column.ParentTable;

                // get query from table
                var query = parentTable.GetQuery(context);

                // get item
                var selectedItem = query.Provider.Execute(
                    query.BuildSingleItemQuery(
                        parentTable,
                        DefaultValue.CsvToArray()));

                // populate list control
                PopulateAllListControls(selectedItem);
            }
            else
            {
                // fill last parent filter
                var lastParentIndex = filters.Count - 1;
                var parentTable = filters[lastParentIndex].Column.ParentTable;
                var parentQuery = parentTable.GetQuery(context);

                // set next descendant list control
                PopulateListControl(lastParentIndex, parentQuery);
            }
        }

        /// <summary>
        /// Sets the default values.
        /// </summary>
        /// <param name="fieldValue">The value.</param>
        private void PopulateAllListControls(object fieldValue)
        {
            var displayStrings = new SortedList<int, String>();

            #region Get list of property values
            // get property values
            var propertyValues = new SortedList<int, Object>();
            propertyValues.Add(0, fieldValue);
            for (int i = 0; i < filters.Count - 1; i++)
            {
                var parentName = filters[i].ParentColumn.Name;
                object pv = propertyValues[i].GetPropertyValue(parentName);
                propertyValues.Add(i + 1, pv);
            }
            #endregion

            // stating at the first filter and work way up to the last filter
            for (int i = 0; i < filters.Count; i++)
            {
                var parentTable = filters[i].Column.ParentTable;
                var parentQuery = parentTable.GetQuery(context);
                IQueryable listItemsQuery;
                if (i == cascadeHierarchicalAttribute.Parameters.Length)
                {
                    listItemsQuery = parentQuery.GetQueryOrdered(parentTable);
                }
                else
                {
                    var pcol = filters[i + 1].Column;
                    var selectedValue = filters[i].ParentColumn.GetForeignKeyString(propertyValues[i]);
                    listItemsQuery = parentQuery.GetQueryFilteredFkColumn(pcol, selectedValue);
                }

                // set next descendant list control
                PopulateListControl(i, listItemsQuery);

                // set initial values
                var selectedValueString = filters[i].Column.Table.GetPrimaryKeyString(propertyValues[i]);
                ListItem item = filters[i].ListControl.Items.FindByValue(selectedValueString);
                if (item != null)
                    filters[i].ListControl.SelectedValue = selectedValueString;
            }
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the parentListControls control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        /// <summary>
        /// Setups the parent list control.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="filterIndex">The parent id.</param>
        /// <param name="items">The items.</param>
        public void PopulateListControl(int filterIndex, IQueryable items)
        {
            // clear the list controls list property
            filters[filterIndex].ListControl.Items.Clear();
            // enable list control
            filters[filterIndex].ListControl.Enabled = true;

            // add unselected value showing the column name
            // [Styles]
            filters[filterIndex].ListControl.Items.Add(
                new ListItem(String.Format("[{0}]",
                    filters[filterIndex].Column.DisplayName), NullValueString));

            foreach (var row in items)
            {
                // populate each item with the display string and key value
                filters[filterIndex].ListControl.Items.Add(
                    new ListItem(filters[filterIndex].Column.ParentTable.GetDisplayString(row),
                    filters[filterIndex].Column.ParentTable.GetPrimaryKeyString(row)));
            }
        }

        /// <summary>
        /// Resets all descendant list controls.
        /// </summary>
        /// <param name="startFrom">The start from.</param>
        private void ResetAllDescendantListControls(int startFrom)
        {
            for (int i = startFrom - 1; i >= 0; i--)
            {
                filters[i].ListControl.Items.Clear();
                filters[i].ListControl.Items.Add(new ListItem("----", String.Empty));
                filters[i].ListControl.Enabled = false;
            }
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event for each List control, 
        /// and populates the next list control in the hierarchy.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        void ListControls_SelectedIndexChanged(object sender, EventArgs e)
        {
            // get list control
            var listControl = (ListControl)sender;

            // get the sending list controls id as an int
            var id = ((Control)sender).ID;

            // use regular expression to find list control index
            var regEx = new Regex(@"\d+");
            var parentIndex = int.Parse(regEx.Match(id).Value);

            if (listControl.SelectedValue != NullValueString)
            {
                if (parentIndex > 0)
                {
                    // set child index
                    var childIndex = parentIndex - 1;

                    // get parent table
                    var parentTable = filters[childIndex].Column.ParentTable;

                    // get query from table
                    var query = parentTable.GetQuery(context);

                    // get items for list control
                    var itemQuery = query.GetQueryFilteredFkColumn(
                        filters[parentIndex].Column,
                        listControl.SelectedValue);

                    // populate list control
                    PopulateListControl(childIndex, itemQuery);

                    // reset all descendant list controls
                    ResetAllDescendantListControls(childIndex);
                }
            }
            else
            {
                // reset all descendant list controls
                ResetAllDescendantListControls(parentIndex);
            }

            OnFilterChanged();
        }

        protected void ListControl0_SelectedIndexChanged(object sender, EventArgs e)
        {
            OnFilterChanged();

            //string selectedValue = filters[0].ListControl.SelectedValue;
            //Page.AddFilterValueToSession(Column, selectedValue);
        }

        public override IQueryable GetQueryable(IQueryable source)
        {
            var fkColumn = Column;
            string selectedValue = String.Empty;
            int selectedEntry = -1;

            for (int i = 0; i < filters.Count; i++)
            {
                if (filters[i].ListControl.HasSelection())
                {
                    selectedEntry = i;
                    selectedValue = filters[i].ListControl.SelectedValue;
                    break;
                }
            }

            // make sure we have a selected value
            if (selectedEntry != -1)
            {
                var propertyPrefix = String.Empty;
                if (selectedEntry > 0)
                {
                    fkColumn = filters[selectedEntry].Column;

                    propertyPrefix = GetPropertyPrefix(source, selectedEntry);
                }

                IDictionary dict = new Hashtable();
                fkColumn.ExtractForeignKey(dict, selectedValue);
                foreach (DictionaryEntry entry in dict)
                {
                    string key = (string)entry.Key;
                    if (DefaultValues != null)
                        DefaultValues[key] = entry.Value;

                    var propertyName = propertyPrefix + fkColumn.GetFilterExpression(key);
                    source = ApplyEqualityFilter(source, propertyName, entry.Value);
                }
            }
            return source;
        }

        /// <summary>
        /// Gets the property prefix.
        /// </summary>
        /// <param name="source">The IQueryable source.</param>
        /// <param name="selectedEntry">The selected dropdown list entry.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private String GetPropertyPrefix(IQueryable source, int selectedEntry)
        {
            // Style.Type.Model.ManufacturerId
            //var param = Expression.Parameter(filters[0].Column.ColumnType, filters[0].Column.Name);
            //var property = Expression.Property(param, filters[1].Column.Name);
            //for (int i = 2; i < selectedEntry; i++)
            //{
            //    var col = filters[i].Column;
            //    property = Expression.Property(property, col.Name);
            //}
            //return property.ToString();
            var param = new StringBuilder();
            for (int i = 0; i < selectedEntry; i++)
            {
                param.Append(filters[i].Column.Name + ".");
            }
            return param.ToString();
        }
    }
}
